package baseFx.common.lock;

import baseFx.common.Assert;
import baseFx.common.AutoCloseable;
import baseFx.common.concurrent.Task;
import baseFx.common.utils.FxDate;
import baseFx.common.utils.RandomUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class AppLockspace implements Lockspace {
    @Override
    public AutoCloseable queueLock(String key, Integer abortTimeout) throws AbortTimeoutException {
        if (abortTimeout == null) {
            abortTimeout = Integer.MAX_VALUE;
        }
        long exp = System.currentTimeMillis() + FxDate.SECONDS * abortTimeout.longValue();
        SimpleLocker locker = new SimpleLocker();
        while (locker.lock(key) == false) {
            if (exp < System.currentTimeMillis()) {
                locker.close();
                throw new AbortTimeoutException(key);
            }
            Task.delay(Long.parseLong(RandomUtils.numByRange(200, 1000)));
        }
        return locker;
    }

    @Override
    public AutoCloseable exLock(String key) throws LockspaceException {
        SimpleLocker locker = new SimpleLocker();
        if (locker.lock(key) == false) {
            locker.close();
            throw new LockspaceException(key + " 已锁定");
        }
        return locker;
    }

    @Override
    public AutoCloseable concurrentLock(String key, int max, Integer abortTimeout) throws AbortTimeoutException {
        if (abortTimeout == null) {
            abortTimeout = Integer.MAX_VALUE;
        }
        if (max < 1) {
            max = 1;
        }
        long exp = System.currentTimeMillis() + FxDate.SECONDS * abortTimeout.longValue();
        ConcurLocker locker = new ConcurLocker();
        while (locker.lock(key, max) == false) {
            if (exp < System.currentTimeMillis()) {
                locker.close();
                throw new AbortTimeoutException(key);
            }
            Task.delay(Long.parseLong(RandomUtils.numByRange(200, 1000)));
        }
        return locker;
    }

    private static class SimpleLocker implements AutoCloseable {
        private static ConcurrentMap<String, Boolean> dataMap = new ConcurrentHashMap<>();
        private String lockKey;

        public boolean lock(String key) {
            Assert.isNull(lockKey, "在解锁之前不能再次上锁");
            if (dataMap.putIfAbsent(key, true) == null) {
                this.lockKey = key;
                return true;
            }
            return false;
        }

        @Override
        public void close() {
            if (lockKey != null) {
                dataMap.remove(lockKey);
                lockKey = null;
            }
        }
    }

    private static class ConcurLocker implements AutoCloseable {
        private final static Map<String, Integer> dataMap = new HashMap<>();
        private String lockkey;

        public boolean lock(String key, int max) {
            Assert.isNull(lockkey, "在解锁之前不能再次上锁");
            synchronized (dataMap) {
                int size = dataMap.getOrDefault(key, 0);
                if (size >= max) {
                    return false;
                }
                dataMap.put(key, size + 1);
                lockkey = key;
                return true;
            }
        }

        @Override
        public void close() {
            if (lockkey != null) {
                synchronized (dataMap) {
                    int size = dataMap.getOrDefault(lockkey, 0);
                    dataMap.put(lockkey, size - 1);
                }
                lockkey = null;
            }
        }
    }
}
